
//**************************************************************************
//**
//** p_inter.c : Heretic 2 : Raven Software, Corp.
//**
//** $Revision: 491 $
//** $Date: 2009-05-30 01:10:42 +0300 (Sat, 30 May 2009) $
//**
//**************************************************************************

#include "h2stdinc.h"
#include "h2def.h"
#include "p_local.h"
#include "soundst.h"

extern int messageson;

#define BONUSADD	6

int ArmorIncrement[NUMCLASSES][NUMARMOR] =
{
	{ 25*FRACUNIT, 20*FRACUNIT, 15*FRACUNIT, 5*FRACUNIT },
	{ 10*FRACUNIT, 25*FRACUNIT, 5*FRACUNIT, 20*FRACUNIT },
	{ 5*FRACUNIT, 15*FRACUNIT, 10*FRACUNIT, 25*FRACUNIT },
#ifdef ASSASSIN
	{ 20*FRACUNIT, 10*FRACUNIT, 25*FRACUNIT, 5*FRACUNIT },
#endif
	{ 0, 0, 0, 0 }
};

int AutoArmorSave[NUMCLASSES] =
{
	15*FRACUNIT,
	10*FRACUNIT,
	5*FRACUNIT,
#ifdef ASSASSIN
	10*FRACUNIT,
#endif
	0
};

const char *TextKeyMessages[] =
{
	TXT_KEY_STEEL,
	TXT_KEY_CAVE,
	TXT_KEY_AXE,
	TXT_KEY_FIRE,
	TXT_KEY_EMERALD,
	TXT_KEY_DUNGEON,
	TXT_KEY_SILVER,
	TXT_KEY_RUSTED,
	TXT_KEY_HORN,
	TXT_KEY_SWAMP,
	TXT_KEY_CASTLE
};

static void SetDormantArtifact(mobj_t *arti);
static void TryPickupArtifact(player_t *player, artitype_t artifactType, mobj_t *artifact);
static void TryPickupWeapon(player_t *player, pclass_t weaponClass, weapontype_t weaponType, mobj_t *weapon, const char *message);
static void TryPickupWeaponPiece(player_t *player, pclass_t matchClass, int pieceValue, mobj_t *pieceMobj);

//--------------------------------------------------------------------------
//
// PROC P_SetMessage
//
//--------------------------------------------------------------------------

void P_SetMessage(player_t *player, const char *message, BOOLEAN ultmsg)
{
	if ((player->ultimateMessage || !messageson) && !ultmsg)
	{
		return;
	}
	if (strlen(message) > 79)
	{
		memcpy(player->message, message, 79);
		player->message[79] = 0;
	}
	else
	{
		strcpy(player->message, message);
	}
	strupr(player->message);
	player->messageTics = MESSAGETICS;
	player->yellowMessage = false;
	if (ultmsg)
	{
		player->ultimateMessage = true;
	}
	if (player == &players[consoleplayer])
	{
		BorderTopRefresh = true;
	}
}

//==========================================================================
//
// P_SetYellowMessage
//
//==========================================================================

void P_SetYellowMessage(player_t *player, const char *message, BOOLEAN ultmsg)
{
	if ((player->ultimateMessage || !messageson) && !ultmsg)
	{
		return;
	}
	if (strlen(message) > 79)
	{
		memcpy(player->message, message, 79);
		player->message[79] = 0;
	}
	else
	{
		strcpy(player->message, message);
	}
	player->messageTics = 5*MESSAGETICS; // Bold messages last longer
	player->yellowMessage = true;
	if (ultmsg)
	{
		player->ultimateMessage = true;
	}
	if (player == &players[consoleplayer])
	{
		BorderTopRefresh = true;
	}
}

//==========================================================================
//
// P_ClearMessage
//
//==========================================================================

void P_ClearMessage(player_t *player)
{
	player->messageTics = 0;
	if (player == &players[consoleplayer])
	{
		BorderTopRefresh = true;
	}
}

//----------------------------------------------------------------------------
//
// PROC P_HideSpecialThing
//
//----------------------------------------------------------------------------

static void P_HideSpecialThing(mobj_t *thing)
{
	thing->flags &= ~MF_SPECIAL;
	thing->flags2 |= MF2_DONTDRAW;
	P_SetMobjState(thing, S_HIDESPECIAL1);
}

//--------------------------------------------------------------------------
//
// FUNC P_GiveMana
//
// Returns true if the player accepted the mana, false if it was
// refused (player has MAX_MANA).
//
//--------------------------------------------------------------------------

BOOLEAN P_GiveMana(player_t *player, manatype_t mana, int count)
{
	int prevMana;
	//weapontype_t changeWeapon;

	if (mana == MANA_NONE || mana == MANA_BOTH)
	{
		return false;
	}
	if (mana < 0 || mana > NUMMANA)
	{
		I_Error("P_GiveMana: bad type %i", mana);
	}
	if (player->mana[mana] == MAX_MANA)
	{
		return false;
	}
	if (gameskill == sk_baby || gameskill == sk_nightmare)
	{ // extra mana in baby mode and nightmare mode
		count += count>>1;
	}
	prevMana = player->mana[mana];

	player->mana[mana] += count;
	if (player->mana[mana] > MAX_MANA)
	{
		player->mana[mana] = MAX_MANA;
	}
	if (player->playerclass == PCLASS_FIGHTER && player->readyweapon == WP_SECOND
						  && mana == MANA_1 && prevMana <= 0)
	{
		P_SetPsprite(player, ps_weapon, S_FAXEREADY_G);
	}
	return true;
}

//==========================================================================
//
// TryPickupWeapon
//
//==========================================================================

static void TryPickupWeapon(player_t *player, pclass_t weaponClass,
	weapontype_t weaponType, mobj_t *weapon, const char *message)
{
	BOOLEAN removeit;
	BOOLEAN gaveMana;
	BOOLEAN gaveWeapon;

	removeit = true;
	if (player->playerclass != weaponClass
#ifdef ASSASSIN
		&& player->playerclass != PCLASS_ASS
#endif
		)
	{ // Wrong class, but try to pick up for mana
		if (netgame && !deathmatch)
		{ // Can't pick up weapons for other classes in coop netplay
			return;
		}
		if (weaponType == WP_SECOND)
		{
			if (!P_GiveMana(player, MANA_1, 25))
			{
				return;
			}
		}
		else
		{
			if (!P_GiveMana(player, MANA_2, 25))
			{
				return;
			}
		}
	}
	else if (netgame && !deathmatch)
	{ // Cooperative net-game
		if (player->weaponowned[weaponType])
		{
			return;
		}
		player->weaponowned[weaponType] = true;
		if (weaponType == WP_SECOND)
		{
			P_GiveMana(player, MANA_1, 25);
		}
		else
		{
			P_GiveMana(player, MANA_2, 25);
		}
		player->pendingweapon = weaponType;
		removeit = false;
	}
	else
	{ // Deathmatch or single player game
		if (weaponType == WP_SECOND)
		{
			gaveMana = P_GiveMana(player, MANA_1, 25);
		}
		else
		{
			gaveMana = P_GiveMana(player, MANA_2, 25);
		}
		if (player->weaponowned[weaponType])
		{
			gaveWeapon = false;
		}
		else
		{
			gaveWeapon = true;
			player->weaponowned[weaponType] = true;
			if (weaponType > player->readyweapon)
			{ // Only switch to more powerful weapons
				player->pendingweapon = weaponType;
			}
		}
		if (!(gaveWeapon || gaveMana))
		{ // Player didn't need the weapon or any mana
			return;
		}
	}

	P_SetMessage(player, message, false);
	if (weapon->special)
	{
		P_ExecuteLineSpecial(weapon->special, weapon->args,
						NULL, 0, player->mo);
		weapon->special = 0;
	}

	if (removeit)
	{
		if (deathmatch && !(weapon->flags2 & MF2_DROPPED))
		{
			P_HideSpecialThing(weapon);
		}
		else
		{
			P_RemoveMobj(weapon);
		}
	}

	player->bonuscount += BONUSADD;
	if (player == &players[consoleplayer])
	{
		S_StartSound(NULL, SFX_PICKUP_WEAPON);
		SB_PaletteFlash(false);
	}
}

//--------------------------------------------------------------------------
//
// FUNC P_GiveWeapon
//
// Returns true if the weapon or its mana was accepted.
//
//--------------------------------------------------------------------------

/*
static BOOLEAN P_GiveWeapon(player_t *player, pclass_t pClass, weapontype_t weapon)
{
	BOOLEAN gaveMana;
	BOOLEAN gaveWeapon;

	if (player->playerclass != pClass)
	{ // player cannot use this weapon, take it anyway, and get mana
		if (netgame && !deathmatch)
		{ // Can't pick up weapons for other classes in coop netplay
			return false;
		}
		if (weapon == WP_SECOND)
		{
			return P_GiveMana(player, MANA_1, 25);
		}
		else
		{
			return P_GiveMana(player, MANA_2, 25);
		}
	}
	if (netgame && !deathmatch)
	{ // Cooperative net-game
		if (player->weaponowned[weapon])
		{
			return false;
		}
		player->bonuscount += BONUSADD;
		player->weaponowned[weapon] = true;
		if (weapon == WP_SECOND)
		{
			P_GiveMana(player, MANA_1, 25);
		}
		else
		{
			P_GiveMana(player, MANA_2, 25);
		}
		player->pendingweapon = weapon;
		if (player == &players[consoleplayer])
		{
			S_StartSound(NULL, SFX_PICKUP_WEAPON);
		}
		return false;
	}
	if (weapon == WP_SECOND)
	{
		gaveMana = P_GiveMana(player, MANA_1, 25);
	}
	else
	{
		gaveMana = P_GiveMana(player, MANA_2, 25);
	}
	if (player->weaponowned[weapon])
	{
		gaveWeapon = false;
	}
	else
	{
		gaveWeapon = true;
		player->weaponowned[weapon] = true;
		if (weapon > player->readyweapon)
		{ // Only switch to more powerful weapons
			player->pendingweapon = weapon;
		}
	}
	return(gaveWeapon || gaveMana);
}
*/

//===========================================================================
//
// P_GiveWeaponPiece
//
//===========================================================================

/*
static BOOLEAN P_GiveWeaponPiece(player_t *player, pclass_t pClass, int piece)
{
	P_GiveMana(player, MANA_1, 20);
	P_GiveMana(player, MANA_2, 20);
	if (player->playerclass != pClass)
	{
		return true;
	}
	else if (player->pieces & piece)
	{ // player already has that weapon piece
		return true;
	}
	player->pieces |= piece;
	if (player->pieces == 7)
	{ // player has built the fourth weapon!
		P_GiveWeapon(player, pClass, WP_FOURTH);
		S_StartSound(player->mo, SFX_WEAPON_BUILD);
	}
	return true;
}
*/

//==========================================================================
//
// TryPickupWeaponPiece
//
//==========================================================================

static void TryPickupWeaponPiece(player_t *player, pclass_t matchClass,
				 int pieceValue, mobj_t *pieceMobj)
{
	BOOLEAN removeit;
	BOOLEAN checkAssembled;
	BOOLEAN gaveWeapon;
	int gaveMana;
	static const char *fourthWeaponText[] =
	{
		TXT_WEAPON_F4,
		TXT_WEAPON_C4,
		TXT_WEAPON_M4,
#ifdef ASSASSIN
		TXT_WEAPON_A4
#endif
	};
	static const char *weaponPieceText[] =
	{
		TXT_QUIETUS_PIECE,
		TXT_WRAITHVERGE_PIECE,
		TXT_BLOODSCOURGE_PIECE,
#ifdef ASSASSIN
		TXT_STAFFOFSET_PIECE
#endif
	};
	static int pieceValueTrans[] =
	{
		0,				// 0: never
		WPIECE1|WPIECE2|WPIECE3,	// WPIECE1 (1)
		WPIECE2|WPIECE3,		// WPIECE2 (2)
		0,				// 3: never
		WPIECE3				// WPIECE3 (4)
	};

	removeit = true;
	checkAssembled = true;
	gaveWeapon = false;
	// Allow assassin to pick up any weapons
	if (player->playerclass != matchClass
#ifdef ASSASSIN
		&& player->playerclass != PCLASS_ASS
#endif
		)
	{ // Wrong class, but try to pick up for mana
		if (netgame && !deathmatch)
		{ // Can't pick up wrong-class weapons in coop netplay
			return;
		}
		checkAssembled = false;
		gaveMana = P_GiveMana(player, MANA_1, 20) +
			   P_GiveMana(player, MANA_2, 20);
		if (!gaveMana)
		{ // Didn't need the mana, so don't pick it up
			return;
		}
	}
	else if (netgame && !deathmatch)
	{ // Cooperative net-game
		if (player->pieces & pieceValue)
		{ // Already has the piece
			return;
		}
		pieceValue = pieceValueTrans[pieceValue];
		P_GiveMana(player, MANA_1, 20);
		P_GiveMana(player, MANA_2, 20);
		removeit = false;
	}
	else
	{ // Deathmatch or single player game
		gaveMana = P_GiveMana(player, MANA_1, 20) +
			   P_GiveMana(player, MANA_2, 20);
		if (player->pieces & pieceValue)
		{ // Already has the piece, check if mana needed
			if (!gaveMana)
			{ // Didn't need the mana, so don't pick it up
				return;
			}
			checkAssembled = false;
		}
	}

	// Pick up the weapon piece
	if (pieceMobj->special)
	{
		P_ExecuteLineSpecial(pieceMobj->special, pieceMobj->args,
							NULL, 0, player->mo);
		pieceMobj->special = 0;
	}
	if (removeit)
	{
		if (deathmatch && !(pieceMobj->flags2 & MF2_DROPPED))
		{
			P_HideSpecialThing(pieceMobj);
		}
		else
		{
			P_RemoveMobj(pieceMobj);
		}
	}
	player->bonuscount += BONUSADD;
	if (player == &players[consoleplayer])
	{
		SB_PaletteFlash(false);
	}

	// Check if fourth weapon assembled
	if (checkAssembled)
	{
		player->pieces |= pieceValue;
		if (player->pieces == (WPIECE1|WPIECE2|WPIECE3))
		{
			gaveWeapon = true;
			player->weaponowned[WP_FOURTH] = true;
			player->pendingweapon = WP_FOURTH;
		}
	}

	if (gaveWeapon)
	{
		P_SetMessage(player, fourthWeaponText[matchClass], false);
		// Play the build-sound full volume for all players
		S_StartSound(NULL, SFX_WEAPON_BUILD);
	}
	else
	{
		P_SetMessage(player, weaponPieceText[matchClass], false);
		if (player == &players[consoleplayer])
		{
			S_StartSound(NULL, SFX_PICKUP_WEAPON);
		}
	}
}

//---------------------------------------------------------------------------
//
// FUNC P_GiveBody
//
// Returns false if the body isn't needed at all.
//
//---------------------------------------------------------------------------

BOOLEAN P_GiveBody(player_t *player, int num)
{
	int max;

	max = MAXHEALTH;
	if (player->morphTics)
	{
		max = MAXMORPHHEALTH;
	}
	if (player->health >= max)
	{
		return false;
	}
	player->health += num;
	if (player->health > max)
	{
		player->health = max;
	}
	player->mo->health = player->health;
	return true;
}

//---------------------------------------------------------------------------
//
// FUNC P_GiveArmor
//
// Returns false if the armor is worse than the current armor.
//
//---------------------------------------------------------------------------

BOOLEAN P_GiveArmor(player_t *player, armortype_t armortype, int amount)
{
	int hits;
	int totalArmor;

	extern int ArmorMax[NUMCLASSES];

	if (amount == -1)
	{
		hits = ArmorIncrement[player->playerclass][armortype];
		if (player->armorpoints[armortype] >= hits)
		{
			return false;
		}
		else
		{
			player->armorpoints[armortype] = hits;
		}
	}
	else
	{
		hits = amount * 5 * FRACUNIT;
		totalArmor = player->armorpoints[ARMOR_ARMOR]
				+ player->armorpoints[ARMOR_SHIELD]
				+ player->armorpoints[ARMOR_HELMET]
				+ player->armorpoints[ARMOR_AMULET]
				+ AutoArmorSave[player->playerclass];
		if (totalArmor < ArmorMax[player->playerclass]*5*FRACUNIT)
		{
			player->armorpoints[armortype] += hits;
		}
		else
		{
			return false;
		}
	}
	return true;
}

//---------------------------------------------------------------------------
//
// PROC P_GiveKey
//
//---------------------------------------------------------------------------

static BOOLEAN P_GiveKey(player_t *player, keytype_t key)
{
	if (player->keys & (1<<key))
	{
		return false;
	}
	player->bonuscount += BONUSADD;
	player->keys |= 1<<key;
	return true;
}

//---------------------------------------------------------------------------
//
// FUNC P_GivePower
//
// Returns true if power accepted.
//
//---------------------------------------------------------------------------

BOOLEAN P_GivePower(player_t *player, powertype_t power)
{
	if (power == pw_invulnerability)
	{
		if (player->powers[power] > BLINKTHRESHOLD)
		{ // Already have it
			return false;
		}
		player->powers[power] = INVULNTICS;
		player->mo->flags2 |= MF2_INVULNERABLE;
		if (player->playerclass == PCLASS_MAGE)
		{
			player->mo->flags2 |= MF2_REFLECTIVE;
		}
		return true;
	}
	if (power == pw_flight)
	{
		if (player->powers[power] > BLINKTHRESHOLD)
		{ // Already have it
			return false;
		}
		player->powers[power] = FLIGHTTICS;
		player->mo->flags2 |= MF2_FLY;
		player->mo->flags |= MF_NOGRAVITY;
		if (player->mo->z <= player->mo->floorz)
		{
			player->flyheight = 10; // thrust the player in the air a bit
		}
		return true;
	}
	if (power == pw_infrared)
	{
		if (player->powers[power] > BLINKTHRESHOLD)
		{ // Already have it
			return false;
		}
		player->powers[power] = INFRATICS;
		return true;
	}
	if (power == pw_speed)
	{
		if (player->powers[power] > BLINKTHRESHOLD)
		{ // Already have it
			return false;
		}
		player->powers[power] = SPEEDTICS;
		return true;
	}
	if (power == pw_minotaur)
	{
		// Doesn't matter if already have power, renew ticker
		player->powers[power] = MAULATORTICS;
		return true;
	}
	/*
	if (power == pw_ironfeet)
	{
		player->powers[power] = IRONTICS;
		return true;
	}
	if (power == pw_strength)
	{
		P_GiveBody(player, 100);
		player->powers[power] = 1;
		return true;
	}
	*/
	if (player->powers[power])
	{
		return false; // already got it
	}
	player->powers[power] = 1;
	return true;
}

//==========================================================================
//
// TryPickupArtifact
//
//==========================================================================

static void TryPickupArtifact(player_t *player, artitype_t artifactType,
							mobj_t *artifact)
{
	static const char *artifactMessages[NUMARTIFACTS] =
	{
		NULL,
		TXT_ARTIINVULNERABILITY,
		TXT_ARTIHEALTH,
		TXT_ARTISUPERHEALTH,
		TXT_ARTIHEALINGRADIUS,
		TXT_ARTISUMMON,
		TXT_ARTITORCH,
		TXT_ARTIEGG,
		TXT_ARTIFLY,
		TXT_ARTIBLASTRADIUS,
		TXT_ARTIPOISONBAG,
		TXT_ARTITELEPORTOTHER,
		TXT_ARTISPEED,
		TXT_ARTIBOOSTMANA,
		TXT_ARTIBOOSTARMOR,
		TXT_ARTITELEPORT,
		TXT_ARTIPUZZSKULL,
		TXT_ARTIPUZZGEMBIG,
		TXT_ARTIPUZZGEMRED,
		TXT_ARTIPUZZGEMGREEN1,
		TXT_ARTIPUZZGEMGREEN2,
		TXT_ARTIPUZZGEMBLUE1,
		TXT_ARTIPUZZGEMBLUE2,
		TXT_ARTIPUZZBOOK1,
		TXT_ARTIPUZZBOOK2,
		TXT_ARTIPUZZSKULL2,
		TXT_ARTIPUZZFWEAPON,
		TXT_ARTIPUZZCWEAPON,
		TXT_ARTIPUZZMWEAPON,
		TXT_ARTIPUZZGEAR,	// All gear pickups use the same text
		TXT_ARTIPUZZGEAR,
		TXT_ARTIPUZZGEAR,
		TXT_ARTIPUZZGEAR
	};

	if (P_GiveArtifact(player, artifactType, artifact))
	{
		if (artifact->special)
		{
			P_ExecuteLineSpecial(artifact->special, artifact->args,
								NULL, 0, NULL);
			artifact->special = 0;
		}
		player->bonuscount += BONUSADD;
		if (artifactType < arti_firstpuzzitem)
		{
			SetDormantArtifact(artifact);
			S_StartSound(artifact, SFX_PICKUP_ARTIFACT);
			P_SetMessage(player, artifactMessages[artifactType], false);
		}
		else
		{ // Puzzle item
			S_StartSound(NULL, SFX_PICKUP_ITEM);
			P_SetMessage(player, artifactMessages[artifactType], true);
			if (!netgame || deathmatch)
			{ // Remove puzzle items if not cooperative netplay
				P_RemoveMobj(artifact);
			}
		}
	}
}

//---------------------------------------------------------------------------
//
// FUNC P_GiveArtifact
//
// Returns true if artifact accepted.
//
//---------------------------------------------------------------------------

BOOLEAN P_GiveArtifact(player_t *player, artitype_t arti, mobj_t *mo)
{
	int i;
	int j;
	BOOLEAN slidePointer;

	slidePointer = false;
	i = 0;
	while (player->inventory[i].type != arti && i < player->inventorySlotNum)
	{
		i++;
	}
	if (i == player->inventorySlotNum)
	{
		if (arti < arti_firstpuzzitem)
		{
			i = 0;
			while (player->inventory[i].type < arti_firstpuzzitem &&
						  i < player->inventorySlotNum)
			{
				i++;
			}
			if (i != player->inventorySlotNum)
			{
				for (j = player->inventorySlotNum; j > i; j--)
				{
					player->inventory[j].count = player->inventory[j-1].count;
					player->inventory[j].type = player->inventory[j-1].type;
					slidePointer = true;
				}
			}
		}
		player->inventory[i].count = 1;
		player->inventory[i].type = arti;
		player->inventorySlotNum++;
	}
	else
	{
		if (arti >= arti_firstpuzzitem && netgame && !deathmatch)
		{ // Can't carry more than 1 puzzle item in coop netplay
			return false;
		}
		if (player->inventory[i].count >= 25)
		{ // Player already has 25 of this item
			return false;
		}
		player->inventory[i].count++;
	}
	if (!player->artifactCount)
	{
		player->readyArtifact = arti;
	}
	else if (player == &players[consoleplayer] && slidePointer
						   && i <= inv_ptr)
	{
		inv_ptr++;
		curpos++;
		if (curpos > 6)
		{
			curpos = 6;
		}
	}
	player->artifactCount++;
	return true;
}

//==========================================================================
//
// SetDormantArtifact
//
// Removes the MF_SPECIAL flag and initiates the artifact pickup
// animation.
//
//==========================================================================

static void SetDormantArtifact(mobj_t *arti)
{
	arti->flags &= ~MF_SPECIAL;
	if (deathmatch && !(arti->flags2 & MF2_DROPPED))
	{
		if (arti->type == MT_ARTIINVULNERABILITY)
		{
			P_SetMobjState(arti, S_DORMANTARTI3_1);
		}
		else if (arti->type == MT_SUMMONMAULATOR ||
				arti->type == MT_ARTIFLY)
		{
			P_SetMobjState(arti, S_DORMANTARTI2_1);
		}
		else
		{
			P_SetMobjState(arti, S_DORMANTARTI1_1);
		}
	}
	else
	{ // Don't respawn
		P_SetMobjState(arti, S_DEADARTI1);
	}
}

//---------------------------------------------------------------------------
//
// PROC A_RestoreArtifact
//
//---------------------------------------------------------------------------

void A_RestoreArtifact(mobj_t *arti)
{
	arti->flags |= MF_SPECIAL;
	P_SetMobjState(arti, arti->info->spawnstate);
	S_StartSound(arti, SFX_RESPAWN);
}

//---------------------------------------------------------------------------
//
// PROC A_RestoreSpecialThing1
//
// Make a special thing visible again.
//
//---------------------------------------------------------------------------

void A_RestoreSpecialThing1(mobj_t *thing)
{
	thing->flags2 &= ~MF2_DONTDRAW;
	S_StartSound(thing, SFX_RESPAWN);
}

//---------------------------------------------------------------------------
//
// PROC A_RestoreSpecialThing2
//
//---------------------------------------------------------------------------

void A_RestoreSpecialThing2(mobj_t *thing)
{
	thing->flags |= MF_SPECIAL;
	P_SetMobjState(thing, thing->info->spawnstate);
}

//---------------------------------------------------------------------------
//
// PROC P_TouchSpecialThing
//
//---------------------------------------------------------------------------

void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher)
{
	player_t *player;
	fixed_t delta;
	int sound;
	BOOLEAN respawn;

	delta = special->z-toucher->z;
	if (delta > toucher->height || delta < -32*FRACUNIT)
	{ // Out of reach
		return;
	}
	if (toucher->health <= 0)
	{ // Toucher is dead
		return;
	}
	sound = SFX_PICKUP_ITEM;
	player = toucher->player;
	respawn = true;
	switch (special->sprite)
	{
	// Items
	case SPR_PTN1: // Item_HealingPotion
		if (!P_GiveBody(player, 10))
		{
			return;
		}
		P_SetMessage(player, TXT_ITEMHEALTH, false);
		break;
	case SPR_ARM1:
		if (!P_GiveArmor(player, ARMOR_ARMOR, -1))
		{
			return;
		}
		P_SetMessage(player, TXT_ARMOR1, false);
		break;
	case SPR_ARM2:
		if (!P_GiveArmor(player, ARMOR_SHIELD, -1))
		{
			return;
		}
		P_SetMessage(player, TXT_ARMOR2, false);
		break;
	case SPR_ARM3:
		if (!P_GiveArmor(player, ARMOR_HELMET, -1))
		{
			return;
		}
		P_SetMessage(player, TXT_ARMOR3, false);
		break;
	case SPR_ARM4:
		if (!P_GiveArmor(player, ARMOR_AMULET, -1))
		{
			return;
		}
		P_SetMessage(player, TXT_ARMOR4, false);
		break;

	// Keys
	case SPR_KEY1:
	case SPR_KEY2:
	case SPR_KEY3:
	case SPR_KEY4:
	case SPR_KEY5:
	case SPR_KEY6:
	case SPR_KEY7:
	case SPR_KEY8:
	case SPR_KEY9:
	case SPR_KEYA:
	case SPR_KEYB:
		if (!P_GiveKey(player, special->sprite-SPR_KEY1))
		{
			return;
		}
		P_SetMessage(player, TextKeyMessages[special->sprite-SPR_KEY1], true);
		sound = SFX_PICKUP_KEY;

		// Check and process the special now in case the key doesn't
		// get removed for coop netplay
		if (special->special)
		{
			P_ExecuteLineSpecial(special->special, special->args,
							NULL, 0, toucher);
			special->special = 0;
		}

		if (!netgame)
		{ // Only remove keys in single player game
			break;
		}
		player->bonuscount += BONUSADD;
		if (player == &players[consoleplayer])
		{
			S_StartSound(NULL, sound);
			SB_PaletteFlash(false);
		}
		return;

	// Artifacts
	case SPR_PTN2:
		TryPickupArtifact(player, arti_health, special);
		return;
	case SPR_SOAR:
		TryPickupArtifact(player, arti_fly, special);
		return;
	case SPR_INVU:
		TryPickupArtifact(player, arti_invulnerability, special);
		return;
	case SPR_SUMN:
		TryPickupArtifact(player, arti_summon, special);
		return;
	case SPR_PORK:
		TryPickupArtifact(player, arti_egg, special);
		return;
	case SPR_SPHL:
		TryPickupArtifact(player, arti_superhealth, special);
		return;
	case SPR_HRAD:
		TryPickupArtifact(player, arti_healingradius, special);
		return;
	case SPR_TRCH:
		TryPickupArtifact(player, arti_torch, special);
		return;
	case SPR_ATLP:
		TryPickupArtifact(player, arti_teleport, special);
		return;
	case SPR_TELO:
		TryPickupArtifact(player, arti_teleportother, special);
		return;
	case SPR_PSBG:
		TryPickupArtifact(player, arti_poisonbag, special);
		return;
	case SPR_SPED:
		TryPickupArtifact(player, arti_speed, special);
		return;
	case SPR_BMAN:
		TryPickupArtifact(player, arti_boostmana, special);
		return;
	case SPR_BRAC:
		TryPickupArtifact(player, arti_boostarmor, special);
		return;
	case SPR_BLST:
		TryPickupArtifact(player, arti_blastradius, special);
		return;

	// Puzzle artifacts
	case SPR_ASKU:
		TryPickupArtifact(player, arti_puzzskull, special);
		return;
	case SPR_ABGM:
		TryPickupArtifact(player, arti_puzzgembig, special);
		return;
	case SPR_AGMR:
		TryPickupArtifact(player, arti_puzzgemred, special);
		return;
	case SPR_AGMG:
		TryPickupArtifact(player, arti_puzzgemgreen1, special);
		return;
	case SPR_AGG2:
		TryPickupArtifact(player, arti_puzzgemgreen2, special);
		return;
	case SPR_AGMB:
		TryPickupArtifact(player, arti_puzzgemblue1, special);
		return;
	case SPR_AGB2:
		TryPickupArtifact(player, arti_puzzgemblue2, special);
		return;
	case SPR_ABK1:
		TryPickupArtifact(player, arti_puzzbook1, special);
		return;
	case SPR_ABK2:
		TryPickupArtifact(player, arti_puzzbook2, special);
		return;
	case SPR_ASK2:
		TryPickupArtifact(player, arti_puzzskull2, special);
		return;
	case SPR_AFWP:
		TryPickupArtifact(player, arti_puzzfweapon, special);
		return;
	case SPR_ACWP:
		TryPickupArtifact(player, arti_puzzcweapon, special);
		return;
	case SPR_AMWP:
		TryPickupArtifact(player, arti_puzzmweapon, special);
		return;
	case SPR_AGER:
		TryPickupArtifact(player, arti_puzzgear1, special);
		return;
	case SPR_AGR2:
		TryPickupArtifact(player, arti_puzzgear2, special);
		return;
	case SPR_AGR3:
		TryPickupArtifact(player, arti_puzzgear3, special);
		return;
	case SPR_AGR4:
		TryPickupArtifact(player, arti_puzzgear4, special);
		return;

	// Mana
	case SPR_MAN1:
		if (!P_GiveMana(player, MANA_1, 15))
		{
			return;
		}
		P_SetMessage(player, TXT_MANA_1, false);
		break;
	case SPR_MAN2:
		if (!P_GiveMana(player, MANA_2, 15))
		{
			return;
		}
		P_SetMessage(player, TXT_MANA_2, false);
		break;
	case SPR_MAN3: // Double Mana Dodecahedron
		if (!P_GiveMana(player, MANA_1, 20))
		{
			if (!P_GiveMana(player, MANA_2, 20))
			{
				return;
			}
		}
		else
		{
			P_GiveMana(player, MANA_2, 20);
		}
		P_SetMessage(player, TXT_MANA_BOTH, false);
		break;

	// 2nd and 3rd Mage Weapons
	case SPR_WMCS: // Frost Shards
		TryPickupWeapon(player, PCLASS_MAGE, WP_SECOND,
				special, TXT_WEAPON_M2);
		return;
	case SPR_WMLG: // Arc of Death
		TryPickupWeapon(player, PCLASS_MAGE, WP_THIRD,
				special, TXT_WEAPON_M3);
		return;

	// 2nd and 3rd Fighter Weapons
	case SPR_WFAX: // Timon's Axe
		TryPickupWeapon(player, PCLASS_FIGHTER, WP_SECOND,
				special, TXT_WEAPON_F2);
		return;
	case SPR_WFHM: // Hammer of Retribution
		TryPickupWeapon(player, PCLASS_FIGHTER, WP_THIRD,
				special, TXT_WEAPON_F3);
		return;

	// 2nd and 3rd Cleric Weapons
	case SPR_WCSS: // Serpent Staff
		TryPickupWeapon(player, PCLASS_CLERIC, WP_SECOND,
				special, TXT_WEAPON_C2);
		return;
	case SPR_WCFM: // Firestorm
		TryPickupWeapon(player, PCLASS_CLERIC, WP_THIRD,
				special, TXT_WEAPON_C3);
		return;

	// Fourth Weapon Pieces
	case SPR_WFR1:
		TryPickupWeaponPiece(player, PCLASS_FIGHTER, WPIECE1, special);
		return;
	case SPR_WFR2:
		TryPickupWeaponPiece(player, PCLASS_FIGHTER, WPIECE2, special);
		return;
	case SPR_WFR3:
		TryPickupWeaponPiece(player, PCLASS_FIGHTER, WPIECE3, special);
		return;
	case SPR_WCH1:
		TryPickupWeaponPiece(player, PCLASS_CLERIC, WPIECE1, special);
		return;
	case SPR_WCH2:
		TryPickupWeaponPiece(player, PCLASS_CLERIC, WPIECE2, special);
		return;
	case SPR_WCH3:
		TryPickupWeaponPiece(player, PCLASS_CLERIC, WPIECE3, special);
		return;
	case SPR_WMS1:
		TryPickupWeaponPiece(player, PCLASS_MAGE, WPIECE1, special);
		return;
	case SPR_WMS2:
		TryPickupWeaponPiece(player, PCLASS_MAGE, WPIECE2, special);
		return;
	case SPR_WMS3:
		TryPickupWeaponPiece(player, PCLASS_MAGE, WPIECE3, special);
		return;
#ifdef ASSASSIN
	/*
	// Don't forget to fix this
	case SPR_WAS1:
		TryPickupWeaponPiece(player, PCLASS_ASS, WPIECE1, special);
		return;
	case SPR_WAS2:
		TryPickupWeaponPiece(player, PCLASS_ASS, WPIECE2, special);
		return;
	case SPR_WAS3:
		TryPickupWeaponPiece(player,PCLASS_ASS, WPIECE3, special);
		return;
	*/
#endif
	default:
		I_Error("P_SpecialThing: Unknown gettable thing");
	}
	if (special->special)
	{
		P_ExecuteLineSpecial(special->special, special->args, NULL,
								0, toucher);
		special->special = 0;
	}
	if (deathmatch && respawn && !(special->flags2 & MF2_DROPPED))
	{
		P_HideSpecialThing(special);
	}
	else
	{
		P_RemoveMobj(special);
	}
	player->bonuscount += BONUSADD;
	if (player == &players[consoleplayer])
	{
		S_StartSound(NULL, sound);
		SB_PaletteFlash(false);
	}
}

// Search thinker list for minotaur
static mobj_t *ActiveMinotaur(player_t *master)
{
	mobj_t *mo;
	player_t *plr;
	thinker_t *think;
	int summontime;

	for (think = thinkercap.next; think != &thinkercap; think = think->next)
	{
		if (think->function != P_MobjThinker)
			continue;
		mo = (mobj_t *)think;
		if (mo->type != MT_MINOTAUR)
			continue;
		if (mo->health <= 0)
			continue;
		if (!(mo->flags & MF_COUNTKILL))
			continue;		// for morphed minotaurs
		if (mo->flags & MF_CORPSE)
			continue;
		summontime = READ_INT32(mo->args);
		if ((leveltime - summontime) >= MAULATORTICS)
			continue;
		plr = ((mobj_t *)mo->special1)->player;
		if (plr == master)
			return mo;
	}
	return NULL;
}


//---------------------------------------------------------------------------
//
// PROC P_KillMobj
//
//---------------------------------------------------------------------------

static void P_KillMobj(mobj_t *source, mobj_t *target)
{
	int dummy;
	mobj_t *master;

	target->flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SKULLFLY|MF_NOGRAVITY);
	target->flags |= MF_CORPSE|MF_DROPOFF;
	target->flags2 &= ~MF2_PASSMOBJ;
	target->height >>= 2;
	if ((target->flags & MF_COUNTKILL || target->type == MT_ZBELL) && target->special)
	{ // Initiate monster death actions
		if (target->type == MT_SORCBOSS)
		{
			dummy = 0;
			P_StartACS(target->special, 0, (byte *)&dummy, target, NULL, 0);
		}
		else
		{
			P_ExecuteLineSpecial(target->special, target->args, NULL, 0, target);
		}
	}
	if (source && source->player)
	{ // Check for frag changes
		if (target->player)
		{
			if (target == source)
			{ // Self-frag
				target->player->frags[target->player-players]--;
				if (cmdfrag && netgame &&
					source->player == &players[consoleplayer])
				{ // Send out a frag count packet
					NET_SendFrags(source->player);
				}
			}
			else
			{
				source->player->frags[target->player-players]++;
				if (cmdfrag && netgame &&
					source->player == &players[consoleplayer])
				{ // Send out a frag count packet
					NET_SendFrags(source->player);
				}
			}
		}
	}
	if (target->player)
	{ // Player death
		if (!source)
		{ // Self-frag
			target->player->frags[target->player-players]--;
			if (cmdfrag && netgame &&
				target->player == &players[consoleplayer])
			{ // Send out a frag count packet
				NET_SendFrags(target->player);
			}
		}
		target->flags &= ~MF_SOLID;
		target->flags2 &= ~MF2_FLY;
		target->player->powers[pw_flight] = 0;
		target->player->playerstate = PST_DEAD;
		P_DropWeapon(target->player);
		if (target->flags2 & MF2_FIREDAMAGE)
		{ // Player flame death
			switch (target->player->playerclass)
			{
			case PCLASS_FIGHTER:
				S_StartSound(target, SFX_PLAYER_FIGHTER_BURN_DEATH);
				P_SetMobjState(target, S_PLAY_F_FDTH1);
				return;
			case PCLASS_CLERIC:
				S_StartSound(target, SFX_PLAYER_CLERIC_BURN_DEATH);
				P_SetMobjState(target, S_PLAY_C_FDTH1);
				return;
			case PCLASS_MAGE:
				S_StartSound(target, SFX_PLAYER_MAGE_BURN_DEATH);
				P_SetMobjState(target, S_PLAY_M_FDTH1);
				return;
			default:
				break;
			}
		}
		if (target->flags2 & MF2_ICEDAMAGE)
		{ // Player ice death
			target->flags &= ~(7<<MF_TRANSSHIFT); //no translation
			target->flags |= MF_ICECORPSE;
			switch (target->player->playerclass)
			{
			case PCLASS_FIGHTER:
				P_SetMobjState(target, S_FPLAY_ICE);
				return;
			case PCLASS_CLERIC:
				P_SetMobjState(target, S_CPLAY_ICE);
				return;
			case PCLASS_MAGE:
				P_SetMobjState(target, S_MPLAY_ICE);
				return;
			case PCLASS_PIG:
				P_SetMobjState(target, S_PIG_ICE);
				return;
			default:
				break;
			}
		}
	}
	if (target->flags2 & MF2_FIREDAMAGE)
	{
		if (target->type == MT_FIGHTER_BOSS
			|| target->type == MT_CLERIC_BOSS
			|| target->type == MT_MAGE_BOSS)
		{
			switch (target->type)
			{
			case MT_FIGHTER_BOSS:
				S_StartSound(target, SFX_PLAYER_FIGHTER_BURN_DEATH);
				P_SetMobjState(target, S_PLAY_F_FDTH1);
				return;
			case MT_CLERIC_BOSS:
				S_StartSound(target, SFX_PLAYER_CLERIC_BURN_DEATH);
				P_SetMobjState(target, S_PLAY_C_FDTH1);
				return;
			case MT_MAGE_BOSS:
				S_StartSound(target, SFX_PLAYER_MAGE_BURN_DEATH);
				P_SetMobjState(target, S_PLAY_M_FDTH1);
				return;
			default:
				break;
			}
		}
		else if (target->type == MT_TREEDESTRUCTIBLE)
		{
			P_SetMobjState(target, S_ZTREEDES_X1);
			target->height = 24*FRACUNIT;
			S_StartSound(target, SFX_TREE_EXPLODE);
			return;
		}
	}
	if (target->flags2 & MF2_ICEDAMAGE)
	{
		target->flags |= MF_ICECORPSE;
		switch (target->type)
		{
		case MT_BISHOP:
			P_SetMobjState(target, S_BISHOP_ICE);
			return;
		case MT_CENTAUR:
		case MT_CENTAURLEADER:
			P_SetMobjState(target, S_CENTAUR_ICE);
			return;
		case MT_DEMON:
		case MT_DEMON2:
			P_SetMobjState(target, S_DEMON_ICE);
			return;
		case MT_SERPENT:
		case MT_SERPENTLEADER:
			P_SetMobjState(target, S_SERPENT_ICE);
			return;
		case MT_WRAITH:
		case MT_WRAITHB:
			P_SetMobjState(target, S_WRAITH_ICE);
			return;
		case MT_ETTIN:
			P_SetMobjState(target, S_ETTIN_ICE1);
			return;
		case MT_FIREDEMON:
			P_SetMobjState(target, S_FIRED_ICE1);
			return;
		case MT_FIGHTER_BOSS:
			P_SetMobjState(target, S_FIGHTER_ICE);
			return;
		case MT_CLERIC_BOSS:
			P_SetMobjState(target, S_CLERIC_ICE);
			return;
		case MT_MAGE_BOSS:
			P_SetMobjState(target, S_MAGE_ICE);
			return;
		case MT_PIG:
			P_SetMobjState(target, S_PIG_ICE);
			return;
		default:
			target->flags &= ~MF_ICECORPSE;
			break;
		}
	}

	if (target->type == MT_MINOTAUR)
	{
		master = (mobj_t *)target->special1;
		if (master->health > 0)
		{
			if (!ActiveMinotaur(master->player))
			{
				master->player->powers[pw_minotaur] = 0;
			}
		}
	}
	else if (target->type == MT_TREEDESTRUCTIBLE)
	{
		target->height = 24*FRACUNIT;
	}
	if (target->health < -(target->info->spawnhealth>>1)
				&& target->info->xdeathstate)
	{ // Extreme death
		P_SetMobjState(target, target->info->xdeathstate);
	}
	else
	{ // Normal death
		if ((target->type==MT_FIREDEMON) &&
			(target->z <= target->floorz + 2*FRACUNIT) &&
			(target->info->xdeathstate))
		{
			// This is to fix the imps' staying in fall state
			P_SetMobjState(target, target->info->xdeathstate);
		}
		else
		{
			P_SetMobjState(target, target->info->deathstate);
		}
	}
	target->tics -= P_Random() & 3;
//	I_StartSound(&actor->r, actor->info->deathsound);
}

//---------------------------------------------------------------------------
//
// FUNC P_MinotaurSlam
//
//---------------------------------------------------------------------------

static void P_MinotaurSlam(mobj_t *source, mobj_t *target)
{
	angle_t angle;
	fixed_t thrust;

	angle = R_PointToAngle2(source->x, source->y, target->x, target->y);
	angle >>= ANGLETOFINESHIFT;
	thrust = 16*FRACUNIT + (P_Random()<<10);
	target->momx += FixedMul(thrust, finecosine[angle]);
	target->momy += FixedMul(thrust, finesine[angle]);
	P_DamageMobj(target, NULL, source, HITDICE(4));
	if (target->player)
	{
		target->reactiontime = 14 + (P_Random() & 7);
	}
	source->args[0] = 0;			// Stop charging
}


//---------------------------------------------------------------------------
//
// FUNC P_MorphPlayer
//
// Returns true if the player gets turned into a pig
//
//---------------------------------------------------------------------------

BOOLEAN P_MorphPlayer(player_t *player)
{
	mobj_t *pmo;
	mobj_t *fog;
	mobj_t *beastMo;
	fixed_t x;
	fixed_t y;
	fixed_t z;
	angle_t angle;
	int oldFlags2;

	if (player->powers[pw_invulnerability])
	{ // Immune when invulnerable
		return false;
	}
	if (player->morphTics)
	{ // Player is already a beast
		return false;
	}
	pmo = player->mo;
	x = pmo->x;
	y = pmo->y;
	z = pmo->z;
	angle = pmo->angle;
	oldFlags2 = pmo->flags2;
	P_SetMobjState(pmo, S_FREETARGMOBJ);
	fog = P_SpawnMobj(x, y, z + TELEFOGHEIGHT, MT_TFOG);
	S_StartSound(fog, SFX_TELEPORT);
	beastMo = P_SpawnMobj(x, y, z, MT_PIGPLAYER);
	beastMo->special1 = player->readyweapon;
	beastMo->angle = angle;
	beastMo->player = player;
	player->health = beastMo->health = MAXMORPHHEALTH;
	player->mo = beastMo;
	memset(&player->armorpoints[0], 0, NUMARMOR*sizeof(int));
	player->playerclass = PCLASS_PIG;
	if (oldFlags2 & MF2_FLY)
	{
		beastMo->flags2 |= MF2_FLY;
	}
	player->morphTics = MORPHTICS;
	P_ActivateMorphWeapon(player);
	return true;
}

//---------------------------------------------------------------------------
//
// FUNC P_MorphMonster
//
//---------------------------------------------------------------------------

static BOOLEAN P_MorphMonster(mobj_t *actor)
{
	mobj_t *master, *monster, *fog;
	mobjtype_t moType;
	fixed_t x;
	fixed_t y;
	fixed_t z;
	mobj_t oldMonster;

	if (actor->player)
		return false;
	if (!(actor->flags & MF_COUNTKILL))
		return false;
	if (actor->flags2 & MF2_BOSS)
		return false;
	moType = actor->type;
	switch (moType)
	{
	case MT_PIG:
		return false;
	case MT_FIGHTER_BOSS:
	case MT_CLERIC_BOSS:
	case MT_MAGE_BOSS:
		return false;
	default:
		break;
	}

	oldMonster = *actor;
	x = oldMonster.x;
	y = oldMonster.y;
	z = oldMonster.z;
	P_RemoveMobjFromTIDList(actor);
	P_SetMobjState(actor, S_FREETARGMOBJ);
	fog = P_SpawnMobj(x, y, z+TELEFOGHEIGHT, MT_TFOG);
	S_StartSound(fog, SFX_TELEPORT);
	monster = P_SpawnMobj(x, y, z, MT_PIG);
	monster->special2 = moType;
	monster->special1 = MORPHTICS + P_Random();
	monster->flags |= (oldMonster.flags & MF_SHADOW);
	monster->target = oldMonster.target;
	monster->angle = oldMonster.angle;
	monster->tid = oldMonster.tid;
	monster->special = oldMonster.special;
	P_InsertMobjIntoTIDList(monster, oldMonster.tid);
	memcpy(monster->args, oldMonster.args, 5);

	// check for turning off minotaur power for active icon
	if (moType == MT_MINOTAUR)
	{
		master = (mobj_t *)oldMonster.special1;
		if (master->health > 0)
		{
			if (!ActiveMinotaur(master->player))
			{
				master->player->powers[pw_minotaur] = 0;
			}
		}
	}
	return true;
}

//---------------------------------------------------------------------------
//
// PROC P_AutoUseHealth
//
//---------------------------------------------------------------------------

static void P_AutoUseHealth(player_t *player, int saveHealth)
{
	int i;
	int count;
	int normalCount;
	int normalSlot = 0;
	int superCount;
	int superSlot = 0;

	normalCount = superCount = 0;
	for (i = 0; i < player->inventorySlotNum; i++)
	{
		if (player->inventory[i].type == arti_health)
		{
			normalSlot = i;
			normalCount = player->inventory[i].count;
		}
		else if (player->inventory[i].type == arti_superhealth)
		{
			superSlot = i;
			superCount = player->inventory[i].count;
		}
	}
	if ((gameskill == sk_baby) && (normalCount*25 >= saveHealth))
	{ // Use quartz flasks
		count = (saveHealth + 24) / 25;
		for (i = 0; i < count; i++)
		{
			player->health += 25;
			P_PlayerRemoveArtifact(player, normalSlot);
		}
	}
	else if (superCount*100 >= saveHealth)
	{ // Use mystic urns
		count = (saveHealth + 99) / 100;
		for (i = 0; i < count; i++)
		{
			player->health += 100;
			P_PlayerRemoveArtifact(player, superSlot);
		}
	}
	else if ((gameskill == sk_baby) &&
		 (superCount*100 + normalCount*25 >= saveHealth))
	{ // Use mystic urns and quartz flasks
		count = (saveHealth + 24) / 25;
		saveHealth -= count * 25;
		for (i = 0; i < count; i++)
		{
			player->health += 25;
			P_PlayerRemoveArtifact(player, normalSlot);
		}
		count = (saveHealth + 99) / 100;
		for (i = 0; i < count; i++)
		{
			player->health += 100;
			P_PlayerRemoveArtifact(player, normalSlot);
		}
	}
	player->mo->health = player->health;
}

/*
=================
=
= P_DamageMobj
=
= Damages both enemies and players
= inflictor is the thing that caused the damage
= 		creature or missile, can be NULL (slime, etc)
= source is the thing to target after taking damage
=		creature or NULL
= Source and inflictor are the same for melee attacks
= source can be null for barrel explosions and other environmental stuff
==================
*/

void P_DamageMobj (mobj_t *target, mobj_t *inflictor, mobj_t *source, int damage)
{
	unsigned int ang;
	int saved;
	fixed_t savedPercent;
	player_t *player;
	mobj_t *master;
	fixed_t thrust;
	int temp;
	int i;

	if (!(target->flags & MF_SHOOTABLE))
	{
		// Shouldn't happen
		return;
	}
	if (target->health <= 0)
	{
		if (inflictor && inflictor->flags2 & MF2_ICEDAMAGE)
		{
			return;
		}
		else if (target->flags & MF_ICECORPSE) // frozen
		{
			target->tics = 1;
			target->momx = target->momy = 0;
		}
		return;
	}
	if ((target->flags2 & MF2_INVULNERABLE) && damage < 10000)
	{ // mobj is invulnerable
		if (target->player)
			return;	// for player, no exceptions
		if (inflictor)
		{
			switch (inflictor->type)
			{
			// These inflictors aren't foiled by invulnerability
			case MT_HOLY_FX:
			case MT_POISONCLOUD:
			case MT_FIREBOMB:
				break;
			default:
				return;
			}
		}
		else
		{
			return;
		}
	}
	if (target->player)
	{
		if (damage < 1000 &&
			((target->player->cheats & CF_GODMODE) ||
			 target->player->powers[pw_invulnerability]))
		{
			return;
		}
	}
	if (target->flags & MF_SKULLFLY)
	{
		target->momx = target->momy = target->momz = 0;
	}
	if (target->flags2 & MF2_DORMANT)
	{
		// Invulnerable, and won't wake up
		return;
	}
	player = target->player;
	if (player && gameskill == sk_baby)
	{
		// Take half damage in trainer mode
		damage >>= 1;
	}
	// Special damage types
	if (inflictor)
	{
		switch (inflictor->type)
		{
		case MT_EGGFX:
			if (player)
			{
				P_MorphPlayer(player);
			}
			else
			{
				P_MorphMonster(target);
			}
			return; // Always return
		case MT_TELOTHER_FX1:
		case MT_TELOTHER_FX2:
		case MT_TELOTHER_FX3:
		case MT_TELOTHER_FX4:
		case MT_TELOTHER_FX5:
			if ((target->flags & MF_COUNTKILL) &&
				(target->type != MT_SERPENT) &&
				(target->type != MT_SERPENTLEADER) &&
				(!(target->flags2 & MF2_BOSS)))
			{
				P_TeleportOther(target);
			}
			return;
		case MT_MINOTAUR:
			if (inflictor->flags & MF_SKULLFLY)
			{ // Slam only when in charge mode
				P_MinotaurSlam(inflictor, target);
				return;
			}
			break;
		case MT_BISH_FX:
			// Bishops are just too nasty
			damage >>= 1;
			break;
		case MT_SHARDFX1:
			switch (inflictor->special2)
			{
			case 3:
				damage <<= 3;
				break;
			case 2:
				damage <<= 2;
				break;
			case 1:
				damage <<= 1;
				break;
			default:
				break;
			}
			break;
		case MT_CSTAFF_MISSILE:
			// Cleric Serpent Staff does poison damage
			if (target->player)
			{
				P_PoisonPlayer(target->player, source, 20);
				damage >>= 1;
			}
			break;
		case MT_ICEGUY_FX2:
			damage >>= 1;
			break;
		case MT_POISONDART:
			if (target->player)
			{
				P_PoisonPlayer(target->player, source, 20);
				damage >>= 1;
			}
			break;
		case MT_POISONCLOUD:
			if (target->player)
			{
				if (target->player->poisoncount < 4)
				{
					P_PoisonDamage(target->player, source,
							15 + (P_Random() & 15), false);	// Don't play painsound
					P_PoisonPlayer(target->player, source, 50);
					S_StartSound(target, SFX_PLAYER_POISONCOUGH);
				}
				return;
			}
			else if (!(target->flags & MF_COUNTKILL))
			{ // only damage monsters/players with the poison cloud
				return;
			}
			break;
		case MT_FSWORD_MISSILE:
			if (target->player)
			{
				damage -= damage>>2;
			}
			break;
		default:
			break;
		}
	}
	// Push the target unless source is using the gauntlets
	if (inflictor && (!source || !source->player) &&
		!(inflictor->flags2 & MF2_NODMGTHRUST))
	{
		ang = R_PointToAngle2(inflictor->x, inflictor->y, target->x, target->y);
		//thrust = damage*(FRACUNIT>>3)*100/target->info->mass;
		thrust = damage*(FRACUNIT>>3)*150/target->info->mass;
		// make fall forwards sometimes
		if ((damage < 40) && (damage > target->health) &&
			(target->z-inflictor->z > 64*FRACUNIT) && (P_Random() & 1))
		{
			ang += ANG180;
			thrust *= 4;
		}
		ang >>= ANGLETOFINESHIFT;
		target->momx += FixedMul(thrust, finecosine[ang]);
		target->momy += FixedMul(thrust, finesine[ang]);
	}

	//
	// player specific
	//
	if (player)
	{
		savedPercent = AutoArmorSave[player->playerclass]
				+ player->armorpoints[ARMOR_ARMOR]
				+ player->armorpoints[ARMOR_SHIELD]
				+ player->armorpoints[ARMOR_HELMET]
				+ player->armorpoints[ARMOR_AMULET];
		if (savedPercent)
		{ // armor absorbed some damage
			if (savedPercent > 100*FRACUNIT)
			{
				savedPercent = 100*FRACUNIT;
			}
			for (i = 0; i < NUMARMOR; i++)
			{
				if (player->armorpoints[i])
				{
					player->armorpoints[i] -= 
						FixedDiv(FixedMul(damage<<FRACBITS,
								  ArmorIncrement[player->playerclass][i]),
							 300 * FRACUNIT);
					if (player->armorpoints[i] < 2*FRACUNIT)
					{
						player->armorpoints[i] = 0;
					}
				}
			}
			saved = FixedDiv(FixedMul(damage<<FRACBITS, savedPercent), 100 * FRACUNIT);
			if (saved > savedPercent * 2)
			{
				saved = savedPercent * 2;
			}
			damage -= saved>>FRACBITS;
		}
		if (damage >= player->health &&
			((gameskill == sk_baby) || deathmatch) &&
			!player->morphTics)
		{ // Try to use some inventory health
			P_AutoUseHealth(player, damage-player->health + 1);
		}
		player->health -= damage; // mirror mobj health here for Dave
		if (player->health < 0)
		{
			player->health = 0;
		}
		player->attacker = source;
		player->damagecount += damage; // add damage after armor / invuln
		if (player->damagecount > 100)
		{
			player->damagecount = 100; // teleport stomp does 10k points...
		}
		temp = damage < 100 ? damage : 100;
		if (player == &players[consoleplayer])
		{
			I_Tactile(40, 10, 40 + temp*2);
			SB_PaletteFlash(false);
		}
	}

	//
	// do the damage
	//
	target->health -= damage;
	if (target->health <= 0)
	{ // Death
		if (inflictor)
		{ // check for special fire damage or ice damage deaths
			if (inflictor->flags2 & MF2_FIREDAMAGE)
			{
				if (player && !player->morphTics)
				{ // Check for flame death
					if (target->health > -50 && damage > 25)
					{
						target->flags2 |= MF2_FIREDAMAGE;
					}
				}
				else
				{
					target->flags2 |= MF2_FIREDAMAGE;
				}
			}
			else if (inflictor->flags2 & MF2_ICEDAMAGE)
			{
				target->flags2 |= MF2_ICEDAMAGE;
			}
		}
		if (source && (source->type == MT_MINOTAUR))
		{ // Minotaur's kills go to his master
			master = (mobj_t *)(source->special1);
			// Make sure still alive and not a pointer to fighter head
			if (master->player && (master->player->mo == master))
			{
				source = master;
			}
		}
		if (source && (source->player) &&
			(source->player->readyweapon == WP_FOURTH))
		{
			// Always extreme death from fourth weapon
			target->health = -5000;
		}
		P_KillMobj(source, target);
		return;
	}
	if ((P_Random() < target->info->painchance)
		&& !(target->flags & MF_SKULLFLY))
	{
		if (inflictor && (inflictor->type >= MT_LIGHTNING_FLOOR
			&& inflictor->type <= MT_LIGHTNING_ZAP))
		{
			if (P_Random() < 96)
			{
				target->flags |= MF_JUSTHIT; // fight back!
				P_SetMobjState(target, target->info->painstate);
			}
			else
			{ // "electrocute" the target
				target->frame |= FF_FULLBRIGHT;
				if (target->flags & MF_COUNTKILL && P_Random() < 128 &&
				    !S_GetSoundPlayingInfo(target, SFX_PUPPYBEAT))
				{
					if ((target->type == MT_CENTAUR) ||
						(target->type == MT_CENTAURLEADER) ||
						(target->type == MT_ETTIN))
					{
						S_StartSound(target, SFX_PUPPYBEAT);
					}
				}
			}
		}
		else
		{
			target->flags |= MF_JUSTHIT; // fight back!
			P_SetMobjState(target, target->info->painstate);
			if (inflictor && inflictor->type == MT_POISONCLOUD)
			{
				if (target->flags & MF_COUNTKILL && P_Random() < 128 &&
				    !S_GetSoundPlayingInfo(target, SFX_PUPPYBEAT))
				{
					if ((target->type == MT_CENTAUR) ||
						(target->type == MT_CENTAURLEADER) ||
						(target->type == MT_ETTIN))
					{
						S_StartSound(target, SFX_PUPPYBEAT);
					}
				}
			}
		}
	}
	target->reactiontime = 0; // we're awake now...
	if (!target->threshold && source && !(source->flags2 & MF2_BOSS) &&
	    !(target->type == MT_BISHOP) && !(target->type == MT_MINOTAUR))
	{
		// Target actor is not intent on another actor,
		// so make him chase after source
		if ((target->type == MT_CENTAUR && source->type == MT_CENTAURLEADER) ||
		    (target->type == MT_CENTAURLEADER && source->type == MT_CENTAUR))
		{
			return;
		}
		target->target = source;
		target->threshold = BASETHRESHOLD;
		if (target->state == &states[target->info->spawnstate]
			&& target->info->seestate != S_NULL)
		{
			P_SetMobjState(target, target->info->seestate);
		}
	}
}

//==========================================================================
//
// P_FallingDamage
//
//==========================================================================

void P_FallingDamage(player_t *player)
{
	int damage;
	int mom;
	int dist;

	mom = abs(player->mo->momz);
	dist = FixedMul(mom, 16*FRACUNIT/23);

	if (mom >= 63*FRACUNIT)
	{ // automatic death
		P_DamageMobj(player->mo, NULL, NULL, 10000);
		return;
	}
	damage = ((FixedMul(dist, dist)/10)>>FRACBITS) - 24;
	if (player->mo->momz > -39*FRACUNIT && damage > player->mo->health
		&& player->mo->health != 1)
	{ // No-death threshold
		damage = player->mo->health - 1;
	}
	S_StartSound(player->mo, SFX_PLAYER_LAND);
	P_DamageMobj(player->mo, NULL, NULL, damage);
}

//==========================================================================
//
// P_PoisonPlayer - Sets up all data concerning poisoning
//
//==========================================================================

void P_PoisonPlayer(player_t *player, mobj_t *poisoner, int poison)
{
	if ((player->cheats&CF_GODMODE) || player->powers[pw_invulnerability])
	{
		return;
	}
	player->poisoncount += poison;
	player->poisoner = poisoner;
	if (player->poisoncount > 100)
	{
		player->poisoncount = 100;
	}
}

//==========================================================================
//
// P_PoisonDamage - Similar to P_DamageMobj
//
//==========================================================================

void P_PoisonDamage(player_t *player, mobj_t *source, int damage,
					BOOLEAN playPainSound)
{
	mobj_t *target;
	mobj_t *inflictor;

	target = player->mo;
	inflictor = source;
	if (target->health <= 0)
	{
		return;
	}
	if (target->flags2 & MF2_INVULNERABLE && damage < 10000)
	{ // mobj is invulnerable
		return;
	}
	if (player && gameskill == sk_baby)
	{
		// Take half damage in trainer mode
		damage >>= 1;
	}
	if (damage < 1000 && ((player->cheats & CF_GODMODE)
		|| player->powers[pw_invulnerability]))
	{
		return;
	}
	if (damage >= player->health
		&& ((gameskill == sk_baby) || deathmatch)
		&& !player->morphTics)
	{ // Try to use some inventory health
		P_AutoUseHealth(player, damage-player->health + 1);
	}
	player->health -= damage; // mirror mobj health here for Dave
	if (player->health < 0)
	{
		player->health = 0;
	}
	player->attacker = source;

	//
	// do the damage
	//
	target->health -= damage;
	if (target->health <= 0)
	{ // Death
		target->special1 = damage;
		if (player && inflictor && !player->morphTics)
		{ // Check for flame death
			if ((inflictor->flags2 & MF2_FIREDAMAGE)
				&& (target->health > -50) && (damage > 25))
			{
				target->flags2 |= MF2_FIREDAMAGE;
			}
			if (inflictor->flags2 & MF2_ICEDAMAGE)
			{
				target->flags2 |= MF2_ICEDAMAGE;
			}
		}
		P_KillMobj(source, target);
		return;
	}
	if (!(leveltime & 63) && playPainSound)
	{
		P_SetMobjState(target, target->info->painstate);
	}
	/*
	if ((P_Random() < target->info->painchance)
		&& !(target->flags & MF_SKULLFLY))
	{
		target->flags |= MF_JUSTHIT; // fight back!
		P_SetMobjState(target, target->info->painstate);
	}
	*/
}

